package rs.etf.es.visualization;

import java.awt.BorderLayout;
import java.awt.MouseInfo;
import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;

import rs.etf.es.algorithm.Algorithm;
import rs.etf.es.algorithm.Constants;
import rs.etf.es.algorithm.Node;
import rs.etf.es.algorithm.NodeAddressBook;
import rs.etf.es.algorithm.RequestManager;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
import edu.uci.ics.jung.visualization.control.GraphMouseListener;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;

public class AlgorithmController {

    private static AlgorithmController algorithmController;

    public static AlgorithmController instance() {
	if (algorithmController == null) {
	    algorithmController = new AlgorithmController();
	}

	return algorithmController;
    }

    private AlgorithmController() {
	vertexFactory = new Factory<Node>() {
	    public Node create() {
		Node newNode = new Node();
		// double x = graphLayout.getX(newNode);
		// double y = ((StaticLayout<Node, String>)
		// graphLayout).getY(newNode);
		// int getWidth = view.getPnlInfo().getWidth();
		// x = MouseInfo.getPointerInfo().getLocation().x;
		// y = MouseInfo.getPointerInfo().getLocation().y;
		// newNode.x = (int) x;
		// newNode.y = (int) y;
		newNode.setName("Node" + newNode.hashCode());
		MainWindow.newNodeDialog.setMyNode(newNode);
		int dialogX = MouseInfo.getPointerInfo().getLocation().x;
		int dialogY = MouseInfo.getPointerInfo().getLocation().y;
		MainWindow.newNodeDialog.setLocation(dialogX, dialogY);
		MainWindow.newNodeDialog.setVisible(true);
		return newNode;
	    }
	};

	edgeFactory = new Factory<String>() {
	    int edgeCount = 0;

	    public String create() {
		return "Edge " + edgeCount++;
	    }
	};
    }

    // ***************** properties from view *************************

    private MainWindow view;

    // ***************** graph properties ******************************
    Graph<Node, String> bayesianNetwork;
    private ScalingControl scaler;
    private VisualizationViewer<Node, String> viewer;
    StaticLayoutWithPositions<Node, String> graphLayout;

    // factories
    Factory<Node> vertexFactory;
    Factory<String> edgeFactory;

    EditingModalGraphMouse<Node, String> editingMouse;

    private boolean creatingMode = false;

    public void createGraph() {

	bayesianNetwork = new DirectedSparseGraph<Node, String>();

	// fill graph

	// add all vertices
	ArrayList<Node> allNodesInGraph = NodeAddressBook.instance().getAllNodes();
	for (Node i : allNodesInGraph) {
	    bayesianNetwork.addVertex(i);
	}

	// add all links
	for (Node i : allNodesInGraph) {
	    for (Node c : i.getChildren()) {
		bayesianNetwork.addEdge(i.getName() + "->" + c.getName(), i, c, EdgeType.DIRECTED);
	    }
	}

	setUpGraph();

    }

    private void setUpGraph() {
	Transformer<Node, Point2D> locationTransformer = new Transformer<Node, Point2D>() {

	    @Override
	    public Point2D transform(Node vertex) {
		return new Point2D.Double(vertex.x, vertex.y);
	    }

	};

	graphLayout = new StaticLayoutWithPositions<Node, String>(bayesianNetwork, locationTransformer);

	viewer = new VisualizationViewer<Node, String>(graphLayout);
	viewer.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Node>());

	view.getPnlGraphicArea().removeAll();
	view.getPnlGraphicArea().add(viewer, BorderLayout.CENTER);

	scaler = new CrossoverScalingControl();

	GraphMouseListener<Node> picker = new VertexPicker(view);
	viewer.addGraphMouseListener(picker);
	viewer.addMouseListener(new PickingGraphMousePlugin<Node, String>());

	DefaultModalGraphMouse<Node, String> gm = new DefaultModalGraphMouse<Node, String>();
	gm.setMode(ModalGraphMouse.Mode.TRANSFORMING);
	viewer.setGraphMouse(gm);

	view.getFrmBayesianNetworkSimulator().validate();
    }

    public void enterCreatingMode() {
	if (!creatingMode) {
	    creatingMode = true;

	    bayesianNetwork = new DirectedSparseGraph<Node, String>();
	    setUpGraph();

	    editingMouse = new EditingModalGraphMouse<Node, String>(viewer.getRenderContext(), vertexFactory, edgeFactory);
	    viewer.setGraphMouse(editingMouse);
	}
    }

    public void zoomIn() {
	scaler.scale(viewer, Constants.ZOOM_IN, viewer.getCenter());
    }

    public void zoomOut() {
	scaler.scale(viewer, Constants.ZOOM_OUT, viewer.getCenter());
    }

    public void setData(MainWindow mainWindow) {
	view = mainWindow;
    }

    public void createDemoData() {
	String[] hVals = { "h1", "h2" };
	String[] bVals = { "b1", "b2" };
	String[] lVals = { "l1", "l2" };
	String[] cVals = { "c1", "c2" };
	Node h = new Node();
	Node b = new Node();
	Node l = new Node();
	Node c = new Node();

	// set names
	h.setName("H");
	b.setName("B");
	l.setName("L");
	c.setName("C");

	h.setAllValues(hVals);
	b.setAllValues(bVals);
	c.setAllValues(cVals);
	l.setAllValues(lVals);

	ArrayList<Node> bParent = new ArrayList<Node>();
	ArrayList<Node> lParents = new ArrayList<Node>();
	ArrayList<Node> cParents = new ArrayList<Node>();
	ArrayList<Node> hChildren = new ArrayList<Node>();
	ArrayList<Node> lChildren = new ArrayList<Node>();
	hChildren.add(b);
	hChildren.add(l);
	lChildren.add(c);
	cParents.add(l);
	bParent.add(h);
	lParents.add(h);
	b.setParents(bParent);
	l.setParents(lParents);
	c.setParents(cParents);
	h.setChildren(hChildren);
	l.setChildren(lChildren);

	// fill node address book
	NodeAddressBook.instance().addNode(c);
	NodeAddressBook.instance().addNode(l);
	NodeAddressBook.instance().addNode(b);
	NodeAddressBook.instance().addNode(h);
	NodeAddressBook.instance().setRoot(h);

	// set CPT values
	h.getMyCPT().setProbability(Node.NO_PARENT, "h1", Node.NO_CONDITION, 0.2);
	h.getMyCPT().setProbability(Node.NO_PARENT, "h2", Node.NO_CONDITION, 0.8);

	b.getMyCPT().setProbability("H", "b1", "h1", 0.25);
	b.getMyCPT().setProbability("H", "b2", "h1", 0.75);
	b.getMyCPT().setProbability("H", "b1", "h2", 0.05);
	b.getMyCPT().setProbability("H", "b2", "h2", 0.95);

	l.getMyCPT().setProbability("H", "l1", "h1", 0.003);
	l.getMyCPT().setProbability("H", "l2", "h1", 0.997);
	l.getMyCPT().setProbability("H", "l1", "h2", 0.00005);
	l.getMyCPT().setProbability("H", "l2", "h2", 0.99995);

	c.getMyCPT().setProbability("L", "c1", "l1", 0.6);
	c.getMyCPT().setProbability("L", "c2", "l1", 0.4);
	c.getMyCPT().setProbability("L", "c1", "l2", 0.02);
	c.getMyCPT().setProbability("L", "c2", "l2", 0.98);
    }

    public void loadGraphFromFile(File file) throws IOException {
	invalidateAll();

	FileInputStream inputGraphFis = new FileInputStream(file);
	BufferedReader graphReader = new BufferedReader(new InputStreamReader(inputGraphFis));
	String currentLine = null;

	String nodesLine = graphReader.readLine();
	String[] allNodes = nodesLine.split(Constants.SPLIT_CHARACTER);
	for (String nodeName : allNodes) {
	    nodeName = nodeName.trim();
	    Node newNode = new Node();
	    newNode.setName(nodeName);
	    NodeAddressBook.instance().addNode(newNode);
	}

	// need to set root
	String rootName = nodesLine.split(Constants.SPLIT_CHARACTER)[0].trim();

	while ((currentLine = graphReader.readLine()) != null) {
	    String nextLine = graphReader.readLine();
	    if (nextLine == null || currentLine == null) {
		break;
	    }
	    if (nextLine.equals(Constants.NO_DATA) || nextLine.equals("") || currentLine.equals("")) {
		continue;
	    }
	    String[] commandArray = currentLine.split(Constants.COMMAND_SPLIT_CHARACTER);
	    String command = commandArray[1].trim();
	    String tempCommandNode = commandArray[0].trim();
	    String affectingNode = tempCommandNode.substring(1, tempCommandNode.length());
	    Node node = NodeAddressBook.instance().getNode(affectingNode);
	    if (command.equals(Constants.VALUES)) {
		addValuesToNode(node, nextLine);
		if (node.getName().equals(rootName)) {
		    Node root = NodeAddressBook.instance().getNode(rootName);
		    NodeAddressBook.instance().setRoot(root);
		}
	    } else if (command.equals(Constants.PARENT)) {
		addParents(node, nextLine);
	    } else if (command.equals(Constants.CHILDREN)) {
		addChildren(node, nextLine);
	    } else if (command.equals(Constants.POSITION)) {
		addPositions(node, nextLine);
	    } else if (command.equals(Constants.CPT)) {
		addCptRecord(node, nextLine);
	    }
	}

	graphReader.close();

    }

    private void invalidateAll() {
	NodeAddressBook.instance().setAddressBook(new HashMap<String, Node>());
	NodeAddressBook.instance().markAsRoot(null);
	RequestManager.instance().invalidate();
    }

    private void addCptRecord(Node node, String commaSeparatedData) {
	String[] values = commaSeparatedData.split(Constants.SPLIT_CHARACTER);
	String parentNodeName = values[0].trim();
	String myValue = values[1].trim();
	String parentValue = values[2].trim();
	Double probability = Double.parseDouble(values[3].trim());
	node.getMyCPT().setProbability(parentNodeName, myValue, parentValue, probability);
    }

    private void addValuesToNode(Node node, String commaSeparatedValues) {
	String[] values = commaSeparatedValues.split(Constants.SPLIT_CHARACTER);
	ArrayList<String> nodeValues = new ArrayList<String>();
	for (String val : values) {
	    nodeValues.add(val.trim());
	}

	node.setValues(nodeValues);
    }

    private void addParents(Node node, String commaSeparatedParents) {
	String[] parents = commaSeparatedParents.split(Constants.SPLIT_CHARACTER);
	ArrayList<Node> parentsList = new ArrayList<Node>();
	for (String parent : parents) {
	    parent = parent.trim();
	    Node parentNode = NodeAddressBook.instance().getNode(parent);
	    parentsList.add(parentNode);
	}

	node.setParents(parentsList);
    }

    private void addChildren(Node node, String commaSeparatedChildren) {
	String[] children = commaSeparatedChildren.split(Constants.SPLIT_CHARACTER);
	ArrayList<Node> childrenList = new ArrayList<Node>();
	for (String child : children) {
	    child = child.trim();
	    Node childNode = NodeAddressBook.instance().getNode(child);
	    childrenList.add(childNode);
	}

	node.setChildren(childrenList);
    }

    private void addPositions(Node node, String commaSeparatedPosition) {
	String[] positions = commaSeparatedPosition.split(Constants.SPLIT_CHARACTER);
	int xCoordiante = Integer.parseInt(positions[0].trim());
	int yCoordinate = Integer.parseInt(positions[1].trim());
	node.x = xCoordiante;
	node.y = yCoordinate;
    }

    public void startAlgorithm() {
	// Thread algorithm = new Thread(new PearlAlgorithm());
	Algorithm algorithmClass = new Algorithm();
	Thread algorithm = new Thread(algorithmClass);
	RequestManager.instance().setAlgorithm(algorithmClass);
	algorithm.setName("Algorithm thread");
	algorithm.start();
    }

    public void saveToFile(File file) throws IOException {
	FileOutputStream fos = new FileOutputStream(file);
	BufferedWriter graphWriter = new BufferedWriter(new OutputStreamWriter(fos));

	// append first line
	StringBuilder builder = new StringBuilder();
	builder.append(NodeAddressBook.instance().getRoot().getName());

	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    if (!i.getName().equals(NodeAddressBook.instance().getRoot().getName())) {
		builder.append(", ");
		builder.append(i.getName());
	    }
	}
	builder.append("\n");

	// append positions
	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    builder.append("$");
	    builder.append(i.getName());
	    builder.append(" ");
	    builder.append(Constants.POSITION);
	    builder.append("\n");
	    builder.append(i.x);
	    builder.append(", ");
	    builder.append(i.y);
	    builder.append("\n");
	}

	// append values
	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    builder.append("$");
	    builder.append(i.getName());
	    builder.append(" ");
	    builder.append(Constants.VALUES);
	    builder.append("\n");
	    boolean first = true;
	    for (String value : i.getValues()) {
		if (!first) {
		    builder.append(", ");
		}
		builder.append(value);
		first = false;
	    }
	    builder.append("\n");
	}

	// append parents
	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    if (i.getParents().size() == 0) {
		continue;
	    }
	    builder.append("$");
	    builder.append(i.getName());
	    builder.append(" ");
	    builder.append(Constants.PARENT);
	    builder.append("\n");
	    boolean first = true;
	    for (Node parent : i.getParents()) {
		if (!first) {
		    builder.append(", ");
		}
		builder.append(parent.getName());
		first = false;
	    }
	    builder.append("\n");
	}

	// append children
	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    if (i.getChildren().size() == 0) {
		continue;
	    }
	    builder.append("$");
	    builder.append(i.getName());
	    builder.append(" ");
	    builder.append(Constants.CHILDREN);
	    builder.append("\n");
	    boolean first = true;
	    for (Node child : i.getChildren()) {
		if (!first) {
		    builder.append(", ");
		}
		builder.append(child.getName());
		first = false;
	    }

	    builder.append("\n");
	}

	// append conditional probability table
	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    for (String entry : i.getAllInsertsFromCPT()) {
		builder.append("$");
		builder.append(i.getName());
		builder.append(" ");
		builder.append(Constants.CPT);
		builder.append("\n");
		builder.append(entry);
		builder.append("\n");
	    }
	}

	graphWriter.write(builder.toString());
	graphWriter.close();

    }

    public void finishedGraphDrawing() {
	for (String edge : bayesianNetwork.getEdges()) {
	    // Pair<Node> endpoints = bayesianNetwork.getEndpoints(edge);
	    Node source = bayesianNetwork.getSource(edge);
	    Node destination = bayesianNetwork.getDest(edge);
	    source.getChildren().add(destination);
	    destination.tempParents.add(source);
	}

	for (Node i : NodeAddressBook.instance().getAllNodes()) {
	    ArrayList<Node> parents = new ArrayList<Node>();
	    for (Node j : i.tempParents) {
		parents.add(j);
	    }
	    i.setParents(parents);
	    i.tempParents.clear();
	}

	// set positions
	Map<Node, Point2D> map = graphLayout.getAllPositions();
	for (Node i : map.keySet()) {
	    Point2D postion = map.get(i);
	    i.x = (int) postion.getX();
	    i.y = (int) postion.getY();
	}

	DefaultModalGraphMouse<Node, String> gm = new DefaultModalGraphMouse<Node, String>();
	gm.setMode(ModalGraphMouse.Mode.TRANSFORMING);
	viewer.setGraphMouse(gm);

	view.getBtnFinishCreating().setVisible(false);
	view.enableAlgorithmControllButtons(true);

	creatingMode = false;
    }

}
